package cn.uncode.rpc.core.protocol;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import cn.uncode.rpc.common.CommonConstant;
import cn.uncode.rpc.core.Exporter;
import cn.uncode.rpc.core.Invoker;
import cn.uncode.rpc.core.Protocol;
import cn.uncode.rpc.core.Request;
import cn.uncode.rpc.core.Response;
import cn.uncode.rpc.core.URL;
import cn.uncode.rpc.core.URLParam;
import cn.uncode.rpc.exception.FrameworkException;
import cn.uncode.rpc.filter.Filter;
import cn.uncode.rpc.spi.ExtensionLoader;
import cn.uncode.rpc.spi.SpiMetaComparator;


/**
 * 
 * Decorate the protocol, to add more features.
 *
 * @author fishermen
 * @version V1.0 created at: 2013-5-30
 */

public class ProxyProtocol implements Protocol {

    private Protocol protocol;

    public ProxyProtocol(Protocol protocol) {
        if (protocol == null) {
            throw new FrameworkException("Protocol is null when construct ProtocolFilterDecorator");
        }
        this.protocol = protocol;
    }

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker, URL url) {
        return protocol.export(decorateWithFilter(invoker, url), url);
    }

	@Override
	public <T> Invoker<T> refer(Class<T> clz, URL url, URL serviceUrl) {
		return decorateWithFilter(protocol.refer(clz, url, serviceUrl), url);
	}

    public void destroy() {
        protocol.destroy();
    }

    /**
     * <pre>
	 * 获取方式：
	 * 1）先获取默认的filter列表；
	 * 2）根据filter配置获取新的filters，并和默认的filter列表合并；
	 * 3）再根据一些其他配置判断是否需要增加其他filter，如根据accessLog进行判断，是否需要增加accesslog
	 * </pre>
     * 
     * @param url
     * @param key
     * @return
     */
    private List<Filter> getFilters(URL url, String key) {

        // load default filters
        List<Filter> filters = new ArrayList<Filter>();
        List<Filter> defaultFilters = ExtensionLoader.getExtensionLoader(Filter.class).getExtensions(key);
        if (defaultFilters != null && defaultFilters.size() > 0) {
            filters.addAll(defaultFilters);
        }

        // add filters via "filter" config
        String filterStr = url.getParameter(URLParam.FILTER.getName());
        if (StringUtils.isNotBlank(filterStr)) {
            String[] filterNames = CommonConstant.COMMA_SPLIT_PATTERN.split(filterStr);
            for (String fn : filterNames) {
                addIfAbsent(filters, fn);
            }
        }

        // add filter via other configs, like accessLog and so on
//        boolean accessLog = url.getBooleanParameter(URLParam.ACCESS_LOG.getName(), URLParam.ACCESS_LOG.getBooleanValue());
//        if (accessLog) {
//            addIfAbsent(filters, AccessLogFilter.class.getAnnotation(SpiMeta.class).name());
//        }

        // sort the filters
        Collections.sort(filters, new SpiMetaComparator<Filter>());
        Collections.reverse(filters);
        return filters;
    }

    private void addIfAbsent(List<Filter> filters, String extensionName) {
        if (StringUtils.isBlank(extensionName)) {
            return;
        }

        Filter extFilter = ExtensionLoader.getExtensionLoader(Filter.class).getExtension(extensionName);
        if (extFilter == null) {
            return;
        }

        boolean exists = false;
        for (Filter f : filters) {
            if (f.getClass() == extFilter.getClass()) {
                exists = true;
                break;
            }
        }
        if (!exists) {
            filters.add(extFilter);
        }

    }
    
    private <T> Invoker<T> decorateWithFilter(Invoker<T> invoker, URL url) {
        List<Filter> filters = getFilters(url, CommonConstant.NODE_TYPE_SERVICE);
        if (filters == null || filters.size() == 0) {
            return invoker;
        }
        Invoker<T> lastInvoker = invoker;
        for (Filter filter : filters) {
            final Filter f = filter;
            final Invoker<T> lp = lastInvoker;
            lastInvoker = new Invoker<T>() {

				@Override
				public Response invoke(Request request) {
					return f.filter(lp, request);
				}

                @Override
                public String desc() {
                    return lp.desc();
                }

                @Override
                public void destroy() {
                    lp.destroy();
                }

                @Override
                public Class<T> getInterface() {
                    return lp.getInterface();
                }

                @Override
                public URL getUrl() {
                    return lp.getUrl();
                }

                @Override
                public void init() {
                    lp.init();
                }

                @Override
                public boolean isAvailable() {
                    return lp.isAvailable();
                }

				@Override
				public int activeInvokeCount() {
					return lp.activeInvokeCount();
				}
            };
        }
        return lastInvoker;
    }

}
